Loading and exploring dataset

library(tidyverse)
library(janitor)
library(lubridate)
library(ggplot2)
library(e1071)
library(modelr)
library(dplyr)
books <- read.csv("books.csv") %>% 
  clean_names()
(books %>% 
  dim())
[1] 11131    12
books_cleaned <- books %>% 
 drop_na(ratings_count) %>% 
  filter(ratings_count != 0) %>% 
  filter(!average_rating %in% c(" Jr./Sam B. Warner",
                             " one of the founding members of this Tolkien website)/Verlyn Flieger/Turgon (=David E. Smith)",
                             " Rawles",
                             " Son & Ferguson"
                            ))

The dataset has 11,131 rows and 12 variables. We are not concerned with books that have no ratings or books that have a rating count of 0. These have been removed.

A number of entries in the “average rating” category are strings of text. These are also removed, we now have 11,043 rows in the dataset.

The 12 variables in the dataset are:

(books_cleaned %>% 
  names())
 [1] "book_id"            "title"              "authors"           
 [4] "average_rating"     "isbn"               "isbn13"            
 [7] "language_code"      "num_pages"          "ratings_count"     
[10] "text_reviews_count" "publication_date"   "publisher"         

book_id, isbn and isbn13 are all unique identifiers for each book. We will remove book_id and isbn and use isbn13 as the most appropriate unique identifier.

num_pages has several entries with ‘0’. This questions the reliability of this variable and so it is removed from our dataset.

books_cleaned <- books_cleaned %>% 
  select(-c(book_id, isbn, num_pages))
(books_cleaned %>% 
  glimpse())
Rows: 11,043
Columns: 9
$ title              <chr> "Harry Potter and the Half-Blood Prince (Harr…
$ authors            <chr> "J.K. Rowling/Mary GrandPré", "J.K. Rowling/M…
$ average_rating     <chr> "4.57", "4.49", "4.42", "4.56", "4.78", "3.74…
$ isbn13             <chr> "9780439785969", "9780439358071", "9780439554…
$ language_code      <chr> "eng", "eng", "eng", "eng", "eng", "en-US", "…
$ ratings_count      <int> 2095690, 2153167, 6333, 2339585, 41428, 19, 2…
$ text_reviews_count <int> 27591, 29221, 244, 36325, 164, 1, 808, 254, 4…
$ publication_date   <chr> "9/16/2006", "9/1/2004", "11/1/2003", "5/1/20…
$ publisher          <chr> "Scholastic Inc.", "Scholastic Inc.", "Schola…

This leaves us with 9 variables.

We now tidy these variables to ensure they are of the correct data type for further analysis.

books_cleaned <- books_cleaned %>% 
  mutate(average_rating = as.double(average_rating),
         language_code = as.factor(language_code),
         publication_date = mdy(publication_date)) %>% 
  drop_na(publication_date)

Two entries have incorrect publications dates (31st June and 31st November) given the unreliability of the data, these entries have also been removed.

Exploring the data: oldest and newest books

ten_oldest_books <- books_cleaned %>% 
  arrange(publication_date) %>% 
  head(10)

(plot_ten_oldest <- ten_oldest_books %>% 
  ggplot(aes(x = publication_date, y = average_rating)) +
  geom_col())

The ten oldest books in the dataset were published between 1900 and 1928. Number of ratings range from 21-332 and average ratings range from 3.91 - 4.35.

ten_newest_books <- books_cleaned %>% 
  arrange(desc(publication_date)) %>% 
  head(10)

(plot_ten_newest <- ten_newest_books %>% 
  ggplot(aes(x = publication_date, y = average_rating)) +
  geom_col())

The 10 newest books in the dataset were published between July 2018 and March 2020. Rating count ranges from 9-56171 and average ratings range from 3.43 to 4.50.

So far the data is showing that there is a narrow range in average rating, regardless of when the book was published or the ratings count.

Exploring the dataset: average ratings ranges

If we round the average rating and compare the number of each rounded rating we get:

books_cleaned_average <- books_cleaned %>% 
  mutate(rounded_rating = round(average_rating), .after = average_rating)
 
 
(average_rating_summary <- books_cleaned_average %>% 
    group_by(rounded_rating) %>% 
    summarise(rounded_rating_count = n()) %>% 
    mutate(percentage = round(rounded_rating_count / 
             sum(rounded_rating_count) * 100, 2))
)

Almost 92% of books have an average rating of 4. So what does it take to get a below average rating (1-3) or an above average rating (5)?

Exploring the dataset: below average books

(rating_two_or_one <- books_cleaned_average %>% 
  filter(rounded_rating <= 2))

With the exception of “Citizen Girl” all of the books with a rating of 2 or 1 have a ratings count of <5 . Citizen Girl has a ratings count of 5415.

This suggests that there is a potential pattern in the number of reviews and the overall average rating. This is explored further by looking a the rating count ranges for each of the average score categories.


(rating_count_ranges <- books_cleaned_average %>% 
  group_by(rounded_rating) %>% 
  summarise(min_review_count = min(ratings_count),
            max_review_count = max(ratings_count))
)
NA
NA
(ratings_boxplot <- books_cleaned_average %>%
  mutate(rounded_rating = as.factor(rounded_rating)) %>% 
  ggplot(aes(x = rounded_rating,
             y= ratings_count)) +
  geom_boxplot())

Due to the range of results it is impossible to infer anything specific from these boxplots; however, there appears to be a few outliers that are perhaps skewing our data. This leads to the question, is there a pattern to what makes more people read and review a book?

We can break our data down to look at the top 5% most reviewed books.

top_5_percent_ratings_count <- books_cleaned_average %>% 
  mutate(top_5_percent_ratings_count = 
           percent_rank(ratings_count) > 0.95) %>% 
  filter(top_5_percent_ratings_count == TRUE)

Summary of the top 5% most reviewed books (552 books):

(top_5_percent_ratings_count %>% 
  summary())
    title             authors          average_rating  rounded_rating 
 Length:552         Length:552         Min.   :3.130   Min.   :3.000  
 Class :character   Class :character   1st Qu.:3.877   1st Qu.:4.000  
 Mode  :character   Mode  :character   Median :4.030   Median :4.000  
                                       Mean   :4.015   Mean   :3.989  
                                       3rd Qu.:4.170   3rd Qu.:4.000  
                                       Max.   :4.590   Max.   :5.000  
                                                                      
    isbn13          language_code ratings_count     text_reviews_count
 Length:552         eng    :531   Min.   :  61639   Min.   :  109     
 Class :character   en-US  : 13   1st Qu.:  84114   1st Qu.: 2356     
 Mode  :character   spa    :  4   Median : 128474   Median : 4014     
                    en-GB  :  2   Mean   : 273958   Mean   : 6946     
                    fre    :  2   3rd Qu.: 248678   3rd Qu.: 7563     
                    ale    :  0   Max.   :4597666   Max.   :94265     
                    (Other):  0                                       
 publication_date      publisher         top_5_percent_ratings_count
 Min.   :1952-12-01   Length:552         Mode:logical               
 1st Qu.:2001-06-12   Class :character   TRUE:552                   
 Median :2004-01-30   Mode  :character                              
 Mean   :2002-12-03                                                 
 3rd Qu.:2006-01-17                                                 
 Max.   :2014-07-29                                                 
                                                                    

Perhaps one area to explore is whether there are publishers that get better reviews than others.

(top_5_percent_ratings_count %>% 
  distinct(publisher) %>% 
  arrange(publisher))

It looks like there is inconsistent naming of publishers, or often one publishing house has multiple divisions that they publsih under. This means looking at publishing info will require a lot of tidying and is likely not worth it.

Exploring the data: publishing languages

Let’s looks at books that have been published in multiple languages

published_more_than_once <- books_cleaned_average %>%
  filter(duplicated(title) | duplicated(title, fromLast = TRUE)) %>%
  arrange(title)

(published_more_than_once %>% 
  distinct(title))

How many languages are represented in the books published more than once?

(published_more_than_once %>% 
   distinct(language_code))

It looks like 9 langauges, but 4 of these are English. We will group “eng”, “en-CA”, “en-GB” and “en-US” together, to be left with 6 distinct languages: - Aleut - English - French - German - Greek - Spanish

#grouping all versions of English together and calculating mean average rating per book, per language.

published_more_than_once <- published_more_than_once %>% 
  mutate(language_code = case_when(
    language_code %in% c("en-CA", "en-GB", "en-US") ~ "eng",
    .default = language_code)
    ) %>% 
    group_by(title, language_code) %>% 
    summarise(average_rating = mean(average_rating))
`summarise()` has grouped output by 'title'. You can override using the `.groups` argument.

Use our grouped data we can look at books that have been published in multiple languages:

published_multiple_languages <- published_more_than_once %>%
  filter(duplicated(title) | duplicated(title, fromLast = TRUE)) %>%
  arrange(title)
  

We find that there are 19 books published in multiple languages:

(published_multiple_languages %>% 
  distinct(title))

Do books do better in different languages, or do they score roughly the same?


(mult_languages_pivot <- published_multiple_languages %>%
  pivot_wider(names_from = language_code, values_from = average_rating))
NA
#adding colour palette

cbb_palette_language <- c("#56B4E9", "#0072B2","#D55E00", "#E69F00", "darkgreen", "#009E73")

published_multiple_languages %>% 
  ggplot(aes(x = title, y = average_rating,
             fill = language_code)) + 
  geom_col(position = "dodge") +
  scale_fill_manual(values = cbb_palette_language) +
  theme_bw() +
    labs(title = "Books published in multiple languages",
       subtitle = "Comparing ratings across languages") +
       theme(
     text = element_text(size = 10),
     axis.text.x = element_text(size = 6, 
                                angle = 45,
                                hjust = 1),
     axis.text.y = element_text(size = 6))

Jane Eyre averaged slightly lower in English than in German (4.11 to 4.12).

Trainspotting scored slightly lower in English than in French (4.03 to 4.09).

Let’s add the variations on English back in to see if this changes anything:

published_more_than_once_english <- books_cleaned_average %>%
  filter(duplicated(title) | duplicated(title, fromLast = TRUE)) %>%
  arrange(title) %>% 
  group_by(title, language_code) %>% 
  summarise(average_rating = mean(average_rating)) %>% 
  filter(duplicated(title) | duplicated(title, fromLast = TRUE))
`summarise()` has grouped output by 'title'. You can override using the `.groups` argument.
  

(mult_languages_pivot_eng <- published_more_than_once_english %>%
  pivot_wider(names_from = language_code, values_from = average_rating))

This hasn’t been looked at in any details yet. It appears that there are minor variations, but nothing that stands out. It might be worth exploring this further. This data could perhaps let publishers and authors get an idea of what languages might be worth publishing in. Or if readers of of variation of English tend to give better reviews.

Changing tact

Looking at at the average rating per language for all books

(language_avg <- books_cleaned_average %>% 
  group_by(language_code) %>%
  summarise(count = n(), mean_avg = mean(average_rating))
)

Need to removed langauges with fewest review numbers. Suggest >20.


#looking at the average review score for languages that appear >20
(language_avg_20 <- books_cleaned_average %>% 
  group_by(language_code) %>%
  filter(n() > 20) %>% 
  summarise(count = n(), mean_avg = mean(average_rating))
)
language_avg_20 %>% 
  ggplot(aes(x = language_code, y = mean_avg, fill = language_code)) + 
  geom_col() +
  theme_bw() +
       theme(
     text = element_text(size = 10),
     axis.text.x = element_text(size = 6, 
                                angle = 45,
                                hjust = 1),
     axis.text.y = element_text(size = 6))

What is the average across all books?

(avg_all_books <- books_cleaned_average %>% 
  summarise(mean_avg = mean(average_rating)))
(language_avg_20_above_below <- language_avg_20 %>% 
   mutate(mean = 3.943) %>% 
   mutate(above_or_below = if_else(mean_avg > mean, "above", "below"))
)
above_below_palette <- c("darkgreen", "darkred")
language_avg_20_above_below %>% 
  filter(language_code != c("eng", "en-US")) %>% 
  ggplot(aes(x = language_code, y = mean_avg, fill = above_or_below)) + 
  geom_col() +
  scale_fill_manual(values = above_below_palette) +
  geom_label(aes(label = round((mean_avg), 2))) +
  theme_bw()
Warning: There were 2 warnings in `filter()`.
The first warning was:
ℹ In argument: `language_code != c("eng", "en-US")`.
Caused by warning in `!=.default`:
! longer object length is not a multiple of shorter object length
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 1 remaining warning.

Maybe there is something here? Take out en-US and eng to leave us with a more comfortable range of review numbers?

Number of pages vs ratings count


#reproducing cleaned data, this time keeping num_pages

books_cleaned_v2 <- books %>% 
  drop_na(ratings_count) %>% 
  filter(ratings_count != 0) %>% 
  filter(!average_rating %in% c(" Jr./Sam B. Warner",
                             " one of the founding members of this Tolkien website)/Verlyn Flieger/Turgon (=David E. Smith)",
                             " Rawles",
                             " Son & Ferguson"
                            )) %>% 
  select(-c(book_id, isbn)) %>% 
  mutate(num_pages = as.integer(num_pages),
         ratings_count = as.integer(ratings_count)) %>% 
  arrange(num_pages)
books_cleaned_v2 %>% 
ggplot(aes(x = num_pages, y = ratings_count)) +
  geom_point() +
  labs(x = "Number of Pages", y = "Number of Ratings")

# how many books have <100 pages?

books_cleaned_v2 %>% 
  filter(num_pages <100)

books_cleaned_v2 %>% 
  filter(num_pages == 0)

books_cleaned_v2 %>% 
  filter(num_pages <10,
         num_pages > 0)
NA

1,010 books have <100 pages, 75 of these have a page count of 0 and 116 have a page count of between 1 & 9. Given the unlikelihood of this many books being this short, it is clear that there are errors in this variable.

There is no known appropriate number for fewest pages a book may have. To try to eliminate any incorrect data, we remove the lowest 5% of num_pages.

Our histogram above also show that there are some outlier books that have a high number of pages. There appears to be a steady range of books with


# checking books with >1000 pages

books_cleaned_v2 %>% 
  filter(num_pages >1000)
NA

There are 215 books with >1000 pages. Some of these appear to be collections of books, e.g. “The Border Trilogy”. For data consistency, we will also remove the lowest 5% of num_pages.


# Removing the bottom and top 5% of page count
num_rows <- nrow(books_cleaned_v2)
bottom_percentage <- 0.05
top_percentage <- 0.95

bottom_rows_to_remove <- round(num_rows * bottom_percentage)
top_rows_to_remove <- round(num_rows * (1 - top_percentage))

# Remove the bottom and top rows
trimmed_books_v1 <- books_cleaned_v2[(bottom_rows_to_remove + 1):(num_rows - top_rows_to_remove), , drop = FALSE]
trimmed_books_v1 %>% 
  ggplot(aes(x = num_pages, y = ratings_count)) +
  geom_point() +
  labs(x = "Number of Pages", y = "Number of Ratings")

trimmed_books_v2 <- trimmed_books_v1 %>% 
  arrange(ratings_count)
# Removing the bottom and top 5% of ratings count
num_rows <- nrow(trimmed_books_v2)
bottom <- 0.05
top <- 0.95

bottom_remove <- round(num_rows * bottom)
top_remove <- round(num_rows * (1 - top))

# Remove the bottom and top rows
trimmed_books_v2 <- trimmed_books_v2[(bottom_remove + 1):(num_rows - top_remove), , drop = FALSE]
trimmed_books_v2 %>% 
  ggplot(aes(x = num_pages, y = ratings_count)) +
  geom_point() +
  labs(x = "Number of Pages", y = "Number of Ratings")

model <- lm(ratings_count ~ num_pages, data = trimmed_books_v2)
summary(model)

Call:
lm(formula = ratings_count ~ num_pages, data = trimmed_books_v2)

Residuals:
   Min     1Q Median     3Q    Max 
 -7850  -4785  -3818   -631  57553 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 3147.9293   261.0676  12.058   <2e-16 ***
num_pages      6.3403     0.7488   8.467   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 10340 on 8943 degrees of freedom
Multiple R-squared:  0.007953,  Adjusted R-squared:  0.007842 
F-statistic: 71.69 on 1 and 8943 DF,  p-value: < 2.2e-16
plot(model)

data1 <- trimmed_books_v2 %>%
  add_predictions(model) 

data1 %>%
  ggplot(aes(x = num_pages)) +
  geom_point(aes(y = ratings_count)) +
  geom_line(aes(y = pred), col = "red")

trimmed_books_v2 %>% 
summarise(skewness = skewness(ratings_count, type = 1))

The data is highly right skewed. In this case we should either look at the median as our stat, or look at standardising the data.

summary(trimmed_books_v2)
    title             authors          average_rating        isbn13          language_code        num_pages    
 Length:8945        Length:8945        Length:8945        Length:8945        Length:8945        Min.   : 51.0  
 Class :character   Class :character   Class :character   Class :character   Class :character   1st Qu.:208.0  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :300.0  
                                                                                                Mean   :316.6  
                                                                                                3rd Qu.:400.0  
                                                                                                Max.   :750.0  
 ratings_count   text_reviews_count publication_date    publisher        
 Min.   :   10   Min.   :   0.0     Length:8945        Length:8945       
 1st Qu.:  165   1st Qu.:  12.0     Class :character   Class :character  
 Median :  859   Median :  52.0     Mode  :character   Mode  :character  
 Mean   : 5155   Mean   : 234.3                                          
 3rd Qu.: 4414   3rd Qu.: 214.0                                          
 Max.   :62954   Max.   :5699.0                                          

num_pages now ranges from 51-750.

Use this to add book range categories:

trimmed_books_with_range <- trimmed_books_v2 %>% 
  mutate(page_range = case_when(
      num_pages <= 150 ~ "51-150",
      num_pages <= 250 ~ "151-250",
      num_pages <= 350 ~ "251-350",
      num_pages <= 450 ~ "351-450",
      num_pages <= 550 ~ "451-550",
      num_pages <= 650 ~ "551-650",
      num_pages <= 750 ~ "651-750"
  )) %>% 
  mutate(page_range = factor(page_range, 
                             levels = c("51-150",
                                        "151-250", 
                                        "251-350", 
                                        "351-450", 
                                        "451-550", 
                                        "551-650", 
                                        "651-750")))
set.seed(42)

sampled_trimmed_books_with_range <- trimmed_books_with_range %>% group_by(page_range) %>% slice_sample(n=100)
sampled_trimmed_books_with_range %>% 
  ggplot(aes(x = page_range, y = ratings_count)) +
  geom_col()

(trimmed_range_summary <- sampled_trimmed_books_with_range %>% 
  group_by(page_range) %>% 
  summarise(median_ratings_count = median(ratings_count),
            num_books = n()))
trimmed_books_with_range_normalised <- trimmed_books_with_range %>%
  mutate(log_ratings_count = log(ratings_count))
trimmed_books_with_range_normalised %>% 
  ggplot(aes(x = page_range, y = log_ratings_count)) +
  geom_col()
(trimmed_range_normalised_summary <- trimmed_books_with_range_normalised %>% 
  group_by(page_range) %>% 
  summarise(mean = mean(log_ratings_count),
            median = median(log_ratings_count),
            num_books = n()))
LS0tCnRpdGxlOiAiR29vZCBSZWFkcyBleHBsb3JhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKIyMgTG9hZGluZyBhbmQgZXhwbG9yaW5nIGRhdGFzZXQKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShqYW5pdG9yKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KG1vZGVscikKbGlicmFyeShkcGx5cikKYGBgCgpgYGB7cn0KYm9va3MgPC0gcmVhZC5jc3YoImJvb2tzLmNzdiIpICU+JSAKICBjbGVhbl9uYW1lcygpCmBgYAoKYGBge3J9Cihib29rcyAlPiUgCiAgZGltKCkpCgpib29rc19jbGVhbmVkIDwtIGJvb2tzICU+JSAKIGRyb3BfbmEocmF0aW5nc19jb3VudCkgJT4lIAogIGZpbHRlcihyYXRpbmdzX2NvdW50ICE9IDApICU+JSAKICBmaWx0ZXIoIWF2ZXJhZ2VfcmF0aW5nICVpbiUgYygiIEpyLi9TYW0gQi4gV2FybmVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIG9uZSBvZiB0aGUgZm91bmRpbmcgbWVtYmVycyBvZiB0aGlzIFRvbGtpZW4gd2Vic2l0ZSkvVmVybHluIEZsaWVnZXIvVHVyZ29uICg9RGF2aWQgRS4gU21pdGgpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIFJhd2xlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBTb24gJiBGZXJndXNvbiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpCmBgYApUaGUgZGF0YXNldCBoYXMgMTEsMTMxIHJvd3MgYW5kIDEyIHZhcmlhYmxlcy4gV2UgYXJlIG5vdCBjb25jZXJuZWQgd2l0aCBib29rcyB0aGF0IGhhdmUgbm8gcmF0aW5ncyBvciBib29rcyB0aGF0IGhhdmUgYSByYXRpbmcgY291bnQgb2YgMC4gVGhlc2UgaGF2ZSBiZWVuIHJlbW92ZWQuCgpBIG51bWJlciBvZiBlbnRyaWVzIGluIHRoZSAiYXZlcmFnZSByYXRpbmciIGNhdGVnb3J5IGFyZSBzdHJpbmdzIG9mIHRleHQuIFRoZXNlIGFyZSBhbHNvIHJlbW92ZWQsIHdlIG5vdyBoYXZlIDExLDA0MyByb3dzIGluIHRoZSBkYXRhc2V0LgoKVGhlIDEyIHZhcmlhYmxlcyBpbiB0aGUgZGF0YXNldCBhcmU6CgpgYGB7cn0KKGJvb2tzX2NsZWFuZWQgJT4lIAogIG5hbWVzKCkpCmBgYApib29rX2lkLCBpc2JuIGFuZCBpc2JuMTMgYXJlIGFsbCB1bmlxdWUgaWRlbnRpZmllcnMgZm9yIGVhY2ggYm9vay4gV2Ugd2lsbCByZW1vdmUgYm9va19pZCBhbmQgaXNibiBhbmQgdXNlIGlzYm4xMyBhcyB0aGUgbW9zdCBhcHByb3ByaWF0ZSB1bmlxdWUgaWRlbnRpZmllci4KCm51bV9wYWdlcyBoYXMgc2V2ZXJhbCBlbnRyaWVzIHdpdGggJzAnLiBUaGlzIHF1ZXN0aW9ucyB0aGUgcmVsaWFiaWxpdHkgb2YgdGhpcyB2YXJpYWJsZSBhbmQgc28gaXQgaXMgcmVtb3ZlZCBmcm9tIG91ciBkYXRhc2V0LgoKYGBge3J9CmJvb2tzX2NsZWFuZWQgPC0gYm9va3NfY2xlYW5lZCAlPiUgCiAgc2VsZWN0KC1jKGJvb2tfaWQsIGlzYm4sIG51bV9wYWdlcykpCmBgYAoKCmBgYHtyfQooYm9va3NfY2xlYW5lZCAlPiUgCiAgZ2xpbXBzZSgpKQpgYGAKVGhpcyBsZWF2ZXMgdXMgd2l0aCA5IHZhcmlhYmxlcy4KCldlIG5vdyB0aWR5IHRoZXNlIHZhcmlhYmxlcyB0byBlbnN1cmUgdGhleSBhcmUgb2YgdGhlIGNvcnJlY3QgZGF0YSB0eXBlIGZvciBmdXJ0aGVyIGFuYWx5c2lzLiAKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmJvb2tzX2NsZWFuZWQgPC0gYm9va3NfY2xlYW5lZCAlPiUgCiAgbXV0YXRlKGF2ZXJhZ2VfcmF0aW5nID0gYXMuZG91YmxlKGF2ZXJhZ2VfcmF0aW5nKSwKICAgICAgICAgbGFuZ3VhZ2VfY29kZSA9IGFzLmZhY3RvcihsYW5ndWFnZV9jb2RlKSwKICAgICAgICAgcHVibGljYXRpb25fZGF0ZSA9IG1keShwdWJsaWNhdGlvbl9kYXRlKSkgJT4lIAogIGRyb3BfbmEocHVibGljYXRpb25fZGF0ZSkKYGBgCgpUd28gZW50cmllcyBoYXZlIGluY29ycmVjdCBwdWJsaWNhdGlvbnMgZGF0ZXMgKDMxc3QgSnVuZSBhbmQgMzFzdCBOb3ZlbWJlcikgZ2l2ZW4gdGhlIHVucmVsaWFiaWxpdHkgb2YgdGhlIGRhdGEsIHRoZXNlIGVudHJpZXMgaGF2ZSBhbHNvIGJlZW4gcmVtb3ZlZC4KCgojIyBFeHBsb3JpbmcgdGhlIGRhdGE6IG9sZGVzdCBhbmQgbmV3ZXN0IGJvb2tzCgpgYGB7cn0KdGVuX29sZGVzdF9ib29rcyA8LSBib29rc19jbGVhbmVkICU+JSAKICBhcnJhbmdlKHB1YmxpY2F0aW9uX2RhdGUpICU+JSAKICBoZWFkKDEwKQoKKHBsb3RfdGVuX29sZGVzdCA8LSB0ZW5fb2xkZXN0X2Jvb2tzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwdWJsaWNhdGlvbl9kYXRlLCB5ID0gYXZlcmFnZV9yYXRpbmcpKSArCiAgZ2VvbV9jb2woKSkKYGBgClRoZSB0ZW4gb2xkZXN0IGJvb2tzIGluIHRoZSBkYXRhc2V0IHdlcmUgcHVibGlzaGVkIGJldHdlZW4gMTkwMCBhbmQgMTkyOC4gCk51bWJlciBvZiByYXRpbmdzIHJhbmdlIGZyb20gMjEtMzMyIGFuZCBhdmVyYWdlIHJhdGluZ3MgcmFuZ2UgZnJvbSAzLjkxIC0gNC4zNS4KCmBgYHtyfQp0ZW5fbmV3ZXN0X2Jvb2tzIDwtIGJvb2tzX2NsZWFuZWQgJT4lIAogIGFycmFuZ2UoZGVzYyhwdWJsaWNhdGlvbl9kYXRlKSkgJT4lIAogIGhlYWQoMTApCgoocGxvdF90ZW5fbmV3ZXN0IDwtIHRlbl9uZXdlc3RfYm9va3MgJT4lIAogIGdncGxvdChhZXMoeCA9IHB1YmxpY2F0aW9uX2RhdGUsIHkgPSBhdmVyYWdlX3JhdGluZykpICsKICBnZW9tX2NvbCgpKQpgYGAKVGhlIDEwIG5ld2VzdCBib29rcyBpbiB0aGUgZGF0YXNldCB3ZXJlIHB1Ymxpc2hlZCBiZXR3ZWVuIEp1bHkgMjAxOCBhbmQgTWFyY2ggMjAyMC4gUmF0aW5nIGNvdW50IHJhbmdlcyBmcm9tIDktNTYxNzEgYW5kIGF2ZXJhZ2UgcmF0aW5ncyByYW5nZSBmcm9tIDMuNDMgdG8gNC41MC4KClNvIGZhciB0aGUgZGF0YSBpcyBzaG93aW5nIHRoYXQgdGhlcmUgaXMgYSBuYXJyb3cgcmFuZ2UgaW4gYXZlcmFnZSByYXRpbmcsIHJlZ2FyZGxlc3Mgb2Ygd2hlbiB0aGUgYm9vayB3YXMgcHVibGlzaGVkIG9yIHRoZSByYXRpbmdzIGNvdW50LiAKCiMjIEV4cGxvcmluZyB0aGUgZGF0YXNldDogYXZlcmFnZSByYXRpbmdzIHJhbmdlcwoKSWYgd2Ugcm91bmQgdGhlIGF2ZXJhZ2UgcmF0aW5nIGFuZCBjb21wYXJlIHRoZSBudW1iZXIgb2YgZWFjaCByb3VuZGVkIHJhdGluZyB3ZSBnZXQ6CgpgYGB7cn0KYm9va3NfY2xlYW5lZF9hdmVyYWdlIDwtIGJvb2tzX2NsZWFuZWQgJT4lIAogIG11dGF0ZShyb3VuZGVkX3JhdGluZyA9IHJvdW5kKGF2ZXJhZ2VfcmF0aW5nKSwgLmFmdGVyID0gYXZlcmFnZV9yYXRpbmcpCiAKIAooYXZlcmFnZV9yYXRpbmdfc3VtbWFyeSA8LSBib29rc19jbGVhbmVkX2F2ZXJhZ2UgJT4lIAogICAgZ3JvdXBfYnkocm91bmRlZF9yYXRpbmcpICU+JSAKICAgIHN1bW1hcmlzZShyb3VuZGVkX3JhdGluZ19jb3VudCA9IG4oKSkgJT4lIAogICAgbXV0YXRlKHBlcmNlbnRhZ2UgPSByb3VuZChyb3VuZGVkX3JhdGluZ19jb3VudCAvIAogICAgICAgICAgICAgc3VtKHJvdW5kZWRfcmF0aW5nX2NvdW50KSAqIDEwMCwgMikpCikKYGBgCkFsbW9zdCA5MiUgb2YgYm9va3MgaGF2ZSBhbiBhdmVyYWdlIHJhdGluZyBvZiA0LiBTbyB3aGF0IGRvZXMgaXQgdGFrZSB0byBnZXQgYSBiZWxvdyBhdmVyYWdlIHJhdGluZyAoMS0zKSBvciBhbiBhYm92ZSBhdmVyYWdlIHJhdGluZyAoNSk/CgojIyBFeHBsb3JpbmcgdGhlIGRhdGFzZXQ6IGJlbG93IGF2ZXJhZ2UgYm9va3MKCmBgYHtyfQoocmF0aW5nX3R3b19vcl9vbmUgPC0gYm9va3NfY2xlYW5lZF9hdmVyYWdlICU+JSAKICBmaWx0ZXIocm91bmRlZF9yYXRpbmcgPD0gMikpCmBgYApXaXRoIHRoZSBleGNlcHRpb24gb2YgIkNpdGl6ZW4gR2lybCIgYWxsIG9mIHRoZSBib29rcyB3aXRoIGEgcmF0aW5nIG9mIDIgb3IgMSBoYXZlIGEgcmF0aW5ncyBjb3VudCBvZiA8NSAuIENpdGl6ZW4gR2lybCBoYXMgYSByYXRpbmdzIGNvdW50IG9mIDU0MTUuCgpUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlcmUgaXMgYSBwb3RlbnRpYWwgcGF0dGVybiBpbiB0aGUgbnVtYmVyIG9mIHJldmlld3MgYW5kIHRoZSBvdmVyYWxsIGF2ZXJhZ2UgcmF0aW5nLiBUaGlzIGlzIGV4cGxvcmVkIGZ1cnRoZXIgYnkgbG9va2luZyBhIHRoZSByYXRpbmcgY291bnQgcmFuZ2VzIGZvciBlYWNoIG9mIHRoZSBhdmVyYWdlIHNjb3JlIGNhdGVnb3JpZXMuCgoKYGBge3J9CgoocmF0aW5nX2NvdW50X3JhbmdlcyA8LSBib29rc19jbGVhbmVkX2F2ZXJhZ2UgJT4lIAogIGdyb3VwX2J5KHJvdW5kZWRfcmF0aW5nKSAlPiUgCiAgc3VtbWFyaXNlKG1pbl9yZXZpZXdfY291bnQgPSBtaW4ocmF0aW5nc19jb3VudCksCiAgICAgICAgICAgIG1heF9yZXZpZXdfY291bnQgPSBtYXgocmF0aW5nc19jb3VudCkpCikKCgpgYGAKYGBge3J9CihyYXRpbmdzX2JveHBsb3QgPC0gYm9va3NfY2xlYW5lZF9hdmVyYWdlICU+JQogIG11dGF0ZShyb3VuZGVkX3JhdGluZyA9IGFzLmZhY3Rvcihyb3VuZGVkX3JhdGluZykpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByb3VuZGVkX3JhdGluZywKICAgICAgICAgICAgIHk9IHJhdGluZ3NfY291bnQpKSArCiAgZ2VvbV9ib3hwbG90KCkpCmBgYAoKRHVlIHRvIHRoZSByYW5nZSBvZiByZXN1bHRzIGl0IGlzIGltcG9zc2libGUgdG8gaW5mZXIgYW55dGhpbmcgc3BlY2lmaWMgZnJvbSB0aGVzZSBib3hwbG90czsgaG93ZXZlciwgdGhlcmUgYXBwZWFycyB0byBiZSBhIGZldyBvdXRsaWVycyB0aGF0IGFyZSBwZXJoYXBzIHNrZXdpbmcgb3VyIGRhdGEuIFRoaXMgbGVhZHMgdG8gdGhlIHF1ZXN0aW9uLCBpcyB0aGVyZSBhIHBhdHRlcm4gdG8gd2hhdCBtYWtlcyBtb3JlIHBlb3BsZSByZWFkIGFuZCByZXZpZXcgYSBib29rPwoKV2UgY2FuIGJyZWFrIG91ciBkYXRhIGRvd24gdG8gbG9vayBhdCB0aGUgdG9wIDUlIG1vc3QgcmV2aWV3ZWQgYm9va3MuCgpgYGB7cn0KdG9wXzVfcGVyY2VudF9yYXRpbmdzX2NvdW50IDwtIGJvb2tzX2NsZWFuZWRfYXZlcmFnZSAlPiUgCiAgbXV0YXRlKHRvcF81X3BlcmNlbnRfcmF0aW5nc19jb3VudCA9IAogICAgICAgICAgIHBlcmNlbnRfcmFuayhyYXRpbmdzX2NvdW50KSA+IDAuOTUpICU+JSAKICBmaWx0ZXIodG9wXzVfcGVyY2VudF9yYXRpbmdzX2NvdW50ID09IFRSVUUpCmBgYAoKU3VtbWFyeSBvZiB0aGUgdG9wIDUlIG1vc3QgcmV2aWV3ZWQgYm9va3MgKDU1MiBib29rcyk6CgpgYGB7cn0KKHRvcF81X3BlcmNlbnRfcmF0aW5nc19jb3VudCAlPiUgCiAgc3VtbWFyeSgpKQpgYGAKUGVyaGFwcyBvbmUgYXJlYSB0byBleHBsb3JlIGlzIHdoZXRoZXIgdGhlcmUgYXJlIHB1Ymxpc2hlcnMgdGhhdCBnZXQgYmV0dGVyIHJldmlld3MgdGhhbiBvdGhlcnMuIAoKYGBge3J9Cih0b3BfNV9wZXJjZW50X3JhdGluZ3NfY291bnQgJT4lIAogIGRpc3RpbmN0KHB1Ymxpc2hlcikgJT4lIAogIGFycmFuZ2UocHVibGlzaGVyKSkKYGBgCkl0IGxvb2tzIGxpa2UgdGhlcmUgaXMgaW5jb25zaXN0ZW50IG5hbWluZyBvZiBwdWJsaXNoZXJzLCBvciBvZnRlbiBvbmUgcHVibGlzaGluZyBob3VzZSBoYXMgbXVsdGlwbGUgZGl2aXNpb25zIHRoYXQgdGhleSBwdWJsc2loIHVuZGVyLiBUaGlzIG1lYW5zIGxvb2tpbmcgYXQgcHVibGlzaGluZyBpbmZvIHdpbGwgcmVxdWlyZSBhIGxvdCBvZiB0aWR5aW5nIGFuZCBpcyBsaWtlbHkgbm90IHdvcnRoIGl0LgoKIyMgRXhwbG9yaW5nIHRoZSBkYXRhOiBwdWJsaXNoaW5nIGxhbmd1YWdlcwoKTGV0J3MgbG9va3MgYXQgYm9va3MgdGhhdCBoYXZlIGJlZW4gcHVibGlzaGVkIGluIG11bHRpcGxlIGxhbmd1YWdlcwoKYGBge3J9CnB1Ymxpc2hlZF9tb3JlX3RoYW5fb25jZSA8LSBib29rc19jbGVhbmVkX2F2ZXJhZ2UgJT4lCiAgZmlsdGVyKGR1cGxpY2F0ZWQodGl0bGUpIHwgZHVwbGljYXRlZCh0aXRsZSwgZnJvbUxhc3QgPSBUUlVFKSkgJT4lCiAgYXJyYW5nZSh0aXRsZSkKCihwdWJsaXNoZWRfbW9yZV90aGFuX29uY2UgJT4lIAogIGRpc3RpbmN0KHRpdGxlKSkKYGBgCgpIb3cgbWFueSBsYW5ndWFnZXMgYXJlIHJlcHJlc2VudGVkIGluIHRoZSBib29rcyBwdWJsaXNoZWQgbW9yZSB0aGFuIG9uY2U/CgpgYGB7cn0KKHB1Ymxpc2hlZF9tb3JlX3RoYW5fb25jZSAlPiUgCiAgIGRpc3RpbmN0KGxhbmd1YWdlX2NvZGUpKQpgYGAKSXQgbG9va3MgbGlrZSA5IGxhbmdhdWdlcywgYnV0IDQgb2YgdGhlc2UgYXJlIEVuZ2xpc2guIFdlIHdpbGwgZ3JvdXAgImVuZyIsICJlbi1DQSIsICJlbi1HQiIgYW5kICJlbi1VUyIgdG9nZXRoZXIsIHRvIGJlIGxlZnQgd2l0aCA2IGRpc3RpbmN0IGxhbmd1YWdlczoKLSBBbGV1dAotIEVuZ2xpc2gKLSBGcmVuY2gKLSBHZXJtYW4KLSBHcmVlawotIFNwYW5pc2gKCmBgYHtyfQojZ3JvdXBpbmcgYWxsIHZlcnNpb25zIG9mIEVuZ2xpc2ggdG9nZXRoZXIgYW5kIGNhbGN1bGF0aW5nIG1lYW4gYXZlcmFnZSByYXRpbmcgcGVyIGJvb2ssIHBlciBsYW5ndWFnZS4KCnB1Ymxpc2hlZF9tb3JlX3RoYW5fb25jZSA8LSBwdWJsaXNoZWRfbW9yZV90aGFuX29uY2UgJT4lIAogIG11dGF0ZShsYW5ndWFnZV9jb2RlID0gY2FzZV93aGVuKAogICAgbGFuZ3VhZ2VfY29kZSAlaW4lIGMoImVuLUNBIiwgImVuLUdCIiwgImVuLVVTIikgfiAiZW5nIiwKICAgIC5kZWZhdWx0ID0gbGFuZ3VhZ2VfY29kZSkKICAgICkgJT4lIAogICAgZ3JvdXBfYnkodGl0bGUsIGxhbmd1YWdlX2NvZGUpICU+JSAKICAgIHN1bW1hcmlzZShhdmVyYWdlX3JhdGluZyA9IG1lYW4oYXZlcmFnZV9yYXRpbmcpKQoKYGBgClVzZSBvdXIgZ3JvdXBlZCBkYXRhIHdlIGNhbiBsb29rIGF0IGJvb2tzIHRoYXQgaGF2ZSBiZWVuIHB1Ymxpc2hlZCBpbiBtdWx0aXBsZSBsYW5ndWFnZXM6CgpgYGB7cn0KcHVibGlzaGVkX211bHRpcGxlX2xhbmd1YWdlcyA8LSBwdWJsaXNoZWRfbW9yZV90aGFuX29uY2UgJT4lCiAgZmlsdGVyKGR1cGxpY2F0ZWQodGl0bGUpIHwgZHVwbGljYXRlZCh0aXRsZSwgZnJvbUxhc3QgPSBUUlVFKSkgJT4lCiAgYXJyYW5nZSh0aXRsZSkKICAKYGBgCgpXZSBmaW5kIHRoYXQgdGhlcmUgYXJlIDE5IGJvb2tzIHB1Ymxpc2hlZCBpbiBtdWx0aXBsZSBsYW5ndWFnZXM6CgpgYGB7cn0KKHB1Ymxpc2hlZF9tdWx0aXBsZV9sYW5ndWFnZXMgJT4lIAogIGRpc3RpbmN0KHRpdGxlKSkKYGBgCgpEbyBib29rcyBkbyBiZXR0ZXIgaW4gZGlmZmVyZW50IGxhbmd1YWdlcywgb3IgZG8gdGhleSBzY29yZSByb3VnaGx5IHRoZSBzYW1lPwoKYGBge3J9CgoobXVsdF9sYW5ndWFnZXNfcGl2b3QgPC0gcHVibGlzaGVkX211bHRpcGxlX2xhbmd1YWdlcyAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbGFuZ3VhZ2VfY29kZSwgdmFsdWVzX2Zyb20gPSBhdmVyYWdlX3JhdGluZykpCgpgYGAKCmBgYHtyfQojYWRkaW5nIGNvbG91ciBwYWxldHRlCgpjYmJfcGFsZXR0ZV9sYW5ndWFnZSA8LSBjKCIjNTZCNEU5IiwgIiMwMDcyQjIiLCIjRDU1RTAwIiwgIiNFNjlGMDAiLCAiZGFya2dyZWVuIiwgIiMwMDlFNzMiKQpgYGAKCgpgYGB7cn0KCnB1Ymxpc2hlZF9tdWx0aXBsZV9sYW5ndWFnZXMgJT4lIAogIGdncGxvdChhZXMoeCA9IHRpdGxlLCB5ID0gYXZlcmFnZV9yYXRpbmcsCiAgICAgICAgICAgICBmaWxsID0gbGFuZ3VhZ2VfY29kZSkpICsgCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY2JiX3BhbGV0dGVfbGFuZ3VhZ2UpICsKICB0aGVtZV9idygpICsKICAgIGxhYnModGl0bGUgPSAiQm9va3MgcHVibGlzaGVkIGluIG11bHRpcGxlIGxhbmd1YWdlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJDb21wYXJpbmcgcmF0aW5ncyBhY3Jvc3MgbGFuZ3VhZ2VzIikgKwogICAgICAgdGhlbWUoCiAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmdsZSA9IDQ1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSksCiAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpKQoKYGBgCkphbmUgRXlyZSBhdmVyYWdlZCBzbGlnaHRseSBsb3dlciBpbiBFbmdsaXNoIHRoYW4gaW4gR2VybWFuICg0LjExIHRvIDQuMTIpLgoKVHJhaW5zcG90dGluZyBzY29yZWQgc2xpZ2h0bHkgbG93ZXIgaW4gRW5nbGlzaCB0aGFuIGluIEZyZW5jaCAoNC4wMyB0byA0LjA5KS4KCkxldCdzIGFkZCB0aGUgdmFyaWF0aW9ucyBvbiBFbmdsaXNoIGJhY2sgaW4gdG8gc2VlIGlmIHRoaXMgY2hhbmdlcyBhbnl0aGluZzoKCmBgYHtyfQpwdWJsaXNoZWRfbW9yZV90aGFuX29uY2VfZW5nbGlzaCA8LSBib29rc19jbGVhbmVkX2F2ZXJhZ2UgJT4lCiAgZmlsdGVyKGR1cGxpY2F0ZWQodGl0bGUpIHwgZHVwbGljYXRlZCh0aXRsZSwgZnJvbUxhc3QgPSBUUlVFKSkgJT4lCiAgYXJyYW5nZSh0aXRsZSkgJT4lIAogIGdyb3VwX2J5KHRpdGxlLCBsYW5ndWFnZV9jb2RlKSAlPiUgCiAgc3VtbWFyaXNlKGF2ZXJhZ2VfcmF0aW5nID0gbWVhbihhdmVyYWdlX3JhdGluZykpICU+JSAKICBmaWx0ZXIoZHVwbGljYXRlZCh0aXRsZSkgfCBkdXBsaWNhdGVkKHRpdGxlLCBmcm9tTGFzdCA9IFRSVUUpKQogIApgYGAKYGBge3J9CgoobXVsdF9sYW5ndWFnZXNfcGl2b3RfZW5nIDwtIHB1Ymxpc2hlZF9tb3JlX3RoYW5fb25jZV9lbmdsaXNoICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBsYW5ndWFnZV9jb2RlLCB2YWx1ZXNfZnJvbSA9IGF2ZXJhZ2VfcmF0aW5nKSkKYGBgCgpUaGlzIGhhc24ndCBiZWVuIGxvb2tlZCBhdCBpbiBhbnkgZGV0YWlscyB5ZXQuIEl0IGFwcGVhcnMgdGhhdCB0aGVyZSBhcmUgbWlub3IgdmFyaWF0aW9ucywgYnV0IG5vdGhpbmcgdGhhdCBzdGFuZHMgb3V0LiBJdCBtaWdodCBiZSB3b3J0aCBleHBsb3JpbmcgdGhpcyBmdXJ0aGVyLiBUaGlzIGRhdGEgY291bGQgcGVyaGFwcyBsZXQgcHVibGlzaGVycyBhbmQgYXV0aG9ycyBnZXQgYW4gaWRlYSBvZiB3aGF0IGxhbmd1YWdlcyBtaWdodCBiZSB3b3J0aCBwdWJsaXNoaW5nIGluLiBPciBpZiByZWFkZXJzIG9mIG9mIHZhcmlhdGlvbiBvZiBFbmdsaXNoIHRlbmQgdG8gZ2l2ZSBiZXR0ZXIgcmV2aWV3cy4gCgojIENoYW5naW5nIHRhY3QKCkxvb2tpbmcgYXQgYXQgdGhlIGF2ZXJhZ2UgcmF0aW5nIHBlciBsYW5ndWFnZSBmb3IgYWxsIGJvb2tzCgpgYGB7cn0KKGxhbmd1YWdlX2F2ZyA8LSBib29rc19jbGVhbmVkX2F2ZXJhZ2UgJT4lIAogIGdyb3VwX2J5KGxhbmd1YWdlX2NvZGUpICU+JQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgbWVhbl9hdmcgPSBtZWFuKGF2ZXJhZ2VfcmF0aW5nKSkKKQpgYGAKTmVlZCB0byByZW1vdmVkIGxhbmdhdWdlcyB3aXRoIGZld2VzdCByZXZpZXcgbnVtYmVycy4gU3VnZ2VzdCA+MjAuCgpgYGB7cn0KCiNsb29raW5nIGF0IHRoZSBhdmVyYWdlIHJldmlldyBzY29yZSBmb3IgbGFuZ3VhZ2VzIHRoYXQgYXBwZWFyID4yMAoobGFuZ3VhZ2VfYXZnXzIwIDwtIGJvb2tzX2NsZWFuZWRfYXZlcmFnZSAlPiUgCiAgZ3JvdXBfYnkobGFuZ3VhZ2VfY29kZSkgJT4lCiAgZmlsdGVyKG4oKSA+IDIwKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpLCBtZWFuX2F2ZyA9IG1lYW4oYXZlcmFnZV9yYXRpbmcpKQopCmBgYAoKYGBge3J9Cmxhbmd1YWdlX2F2Z18yMCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbGFuZ3VhZ2VfY29kZSwgeSA9IG1lYW5fYXZnLCBmaWxsID0gbGFuZ3VhZ2VfY29kZSkpICsgCiAgZ2VvbV9jb2woKSArCiAgdGhlbWVfYncoKSArCiAgICAgICB0aGVtZSgKICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuZ2xlID0gNDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSwKICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNikpCmBgYApXaGF0IGlzIHRoZSBhdmVyYWdlIGFjcm9zcyBhbGwgYm9va3M/CgpgYGB7cn0KKGF2Z19hbGxfYm9va3MgPC0gYm9va3NfY2xlYW5lZF9hdmVyYWdlICU+JSAKICBzdW1tYXJpc2UobWVhbl9hdmcgPSBtZWFuKGF2ZXJhZ2VfcmF0aW5nKSkpCmBgYApgYGB7cn0KKGxhbmd1YWdlX2F2Z18yMF9hYm92ZV9iZWxvdyA8LSBsYW5ndWFnZV9hdmdfMjAgJT4lIAogICBtdXRhdGUobWVhbiA9IDMuOTQzKSAlPiUgCiAgIG11dGF0ZShhYm92ZV9vcl9iZWxvdyA9IGlmX2Vsc2UobWVhbl9hdmcgPiBtZWFuLCAiYWJvdmUiLCAiYmVsb3ciKSkKKQpgYGAKCmBgYHtyfQphYm92ZV9iZWxvd19wYWxldHRlIDwtIGMoImRhcmtncmVlbiIsICJkYXJrcmVkIikKYGBgCgoKYGBge3J9Cmxhbmd1YWdlX2F2Z18yMF9hYm92ZV9iZWxvdyAlPiUgCiAgZmlsdGVyKGxhbmd1YWdlX2NvZGUgIT0gYygiZW5nIiwgImVuLVVTIikpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsYW5ndWFnZV9jb2RlLCB5ID0gbWVhbl9hdmcsIGZpbGwgPSBhYm92ZV9vcl9iZWxvdykpICsgCiAgZ2VvbV9jb2woKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYWJvdmVfYmVsb3dfcGFsZXR0ZSkgKwogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gcm91bmQoKG1lYW5fYXZnKSwgMikpKSArCiAgdGhlbWVfYncoKQpgYGAKCk1heWJlIHRoZXJlIGlzIHNvbWV0aGluZyBoZXJlPyBUYWtlIG91dCBlbi1VUyBhbmQgZW5nIHRvIGxlYXZlIHVzIHdpdGggYSBtb3JlIGNvbWZvcnRhYmxlIHJhbmdlIG9mIHJldmlldyBudW1iZXJzPwoKIyBOdW1iZXIgb2YgcGFnZXMgdnMgcmF0aW5ncyBjb3VudAoKYGBge3J9CgojcmVwcm9kdWNpbmcgY2xlYW5lZCBkYXRhLCB0aGlzIHRpbWUga2VlcGluZyBudW1fcGFnZXMKCmJvb2tzX2NsZWFuZWRfdjIgPC0gYm9va3MgJT4lIAogIGRyb3BfbmEocmF0aW5nc19jb3VudCkgJT4lIAogIGZpbHRlcihyYXRpbmdzX2NvdW50ICE9IDApICU+JSAKICBmaWx0ZXIoIWF2ZXJhZ2VfcmF0aW5nICVpbiUgYygiIEpyLi9TYW0gQi4gV2FybmVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIG9uZSBvZiB0aGUgZm91bmRpbmcgbWVtYmVycyBvZiB0aGlzIFRvbGtpZW4gd2Vic2l0ZSkvVmVybHluIEZsaWVnZXIvVHVyZ29uICg9RGF2aWQgRS4gU21pdGgpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIFJhd2xlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBTb24gJiBGZXJndXNvbiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpICU+JSAKICBzZWxlY3QoLWMoYm9va19pZCwgaXNibikpICU+JSAKICBtdXRhdGUobnVtX3BhZ2VzID0gYXMuaW50ZWdlcihudW1fcGFnZXMpLAogICAgICAgICByYXRpbmdzX2NvdW50ID0gYXMuaW50ZWdlcihyYXRpbmdzX2NvdW50KSkgJT4lIAogIGFycmFuZ2UobnVtX3BhZ2VzKQoKYGBgCgpgYGB7cn0KYm9va3NfY2xlYW5lZF92MiAlPiUgCmdncGxvdChhZXMoeCA9IG51bV9wYWdlcywgeSA9IHJhdGluZ3NfY291bnQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHggPSAiTnVtYmVyIG9mIFBhZ2VzIiwgeSA9ICJOdW1iZXIgb2YgUmF0aW5ncyIpCmBgYAoKYGBge3J9CiMgaG93IG1hbnkgYm9va3MgaGF2ZSA8MTAwIHBhZ2VzPwoKYm9va3NfY2xlYW5lZF92MiAlPiUgCiAgZmlsdGVyKG51bV9wYWdlcyA8MTAwKQoKYm9va3NfY2xlYW5lZF92MiAlPiUgCiAgZmlsdGVyKG51bV9wYWdlcyA9PSAwKQoKYm9va3NfY2xlYW5lZF92MiAlPiUgCiAgZmlsdGVyKG51bV9wYWdlcyA8MTAsCiAgICAgICAgIG51bV9wYWdlcyA+IDApCiAgCmBgYAoKMSwwMTAgYm9va3MgaGF2ZSA8MTAwIHBhZ2VzLCA3NSBvZiB0aGVzZSBoYXZlIGEgcGFnZSBjb3VudCBvZiAwIGFuZCAxMTYgaGF2ZSBhIHBhZ2UgY291bnQgb2YgYmV0d2VlbiAxICYgOS4gR2l2ZW4gdGhlIHVubGlrZWxpaG9vZCBvZiB0aGlzIG1hbnkgYm9va3MgYmVpbmcgdGhpcyBzaG9ydCwgaXQgaXMgY2xlYXIgdGhhdCB0aGVyZSBhcmUgZXJyb3JzIGluIHRoaXMgdmFyaWFibGUuIAoKVGhlcmUgaXMgbm8ga25vd24gYXBwcm9wcmlhdGUgbnVtYmVyIGZvciBmZXdlc3QgcGFnZXMgYSBib29rIG1heSBoYXZlLiBUbyB0cnkgdG8gZWxpbWluYXRlIGFueSBpbmNvcnJlY3QgZGF0YSwgd2UgcmVtb3ZlIHRoZSBsb3dlc3QgNSUgb2YgbnVtX3BhZ2VzLiAKCk91ciBoaXN0b2dyYW0gYWJvdmUgYWxzbyBzaG93IHRoYXQgdGhlcmUgYXJlIHNvbWUgb3V0bGllciBib29rcyB0aGF0IGhhdmUgYSBoaWdoIG51bWJlciBvZiBwYWdlcy4gVGhlcmUgYXBwZWFycyB0byBiZSBhIHN0ZWFkeSByYW5nZSBvZiBib29rcyB3aXRoIAoKYGBge3J9CgojIGNoZWNraW5nIGJvb2tzIHdpdGggPjEwMDAgcGFnZXMKCmJvb2tzX2NsZWFuZWRfdjIgJT4lIAogIGZpbHRlcihudW1fcGFnZXMgPjEwMDApCgpgYGAKVGhlcmUgYXJlIDIxNSBib29rcyB3aXRoID4xMDAwIHBhZ2VzLiBTb21lIG9mIHRoZXNlIGFwcGVhciB0byBiZSBjb2xsZWN0aW9ucyBvZiBib29rcywgZS5nLiAiVGhlIEJvcmRlciBUcmlsb2d5Ii4gRm9yIGRhdGEgY29uc2lzdGVuY3ksIHdlIHdpbGwgYWxzbyByZW1vdmUgdGhlIGxvd2VzdCA1JSBvZiBudW1fcGFnZXMuCgpgYGB7cn0KCiMgUmVtb3ZpbmcgdGhlIGJvdHRvbSBhbmQgdG9wIDUlIG9mIHBhZ2UgY291bnQKbnVtX3Jvd3MgPC0gbnJvdyhib29rc19jbGVhbmVkX3YyKQpib3R0b21fcGVyY2VudGFnZSA8LSAwLjA1CnRvcF9wZXJjZW50YWdlIDwtIDAuOTUKCmJvdHRvbV9yb3dzX3RvX3JlbW92ZSA8LSByb3VuZChudW1fcm93cyAqIGJvdHRvbV9wZXJjZW50YWdlKQp0b3Bfcm93c190b19yZW1vdmUgPC0gcm91bmQobnVtX3Jvd3MgKiAoMSAtIHRvcF9wZXJjZW50YWdlKSkKCiMgUmVtb3ZlIHRoZSBib3R0b20gYW5kIHRvcCByb3dzCnRyaW1tZWRfYm9va3NfdjEgPC0gYm9va3NfY2xlYW5lZF92MlsoYm90dG9tX3Jvd3NfdG9fcmVtb3ZlICsgMSk6KG51bV9yb3dzIC0gdG9wX3Jvd3NfdG9fcmVtb3ZlKSwgLCBkcm9wID0gRkFMU0VdCmBgYAoKYGBge3J9CnRyaW1tZWRfYm9va3NfdjEgJT4lIAogIGdncGxvdChhZXMoeCA9IG51bV9wYWdlcywgeSA9IHJhdGluZ3NfY291bnQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHggPSAiTnVtYmVyIG9mIFBhZ2VzIiwgeSA9ICJOdW1iZXIgb2YgUmF0aW5ncyIpCmBgYAoKYGBge3J9CnRyaW1tZWRfYm9va3NfdjIgPC0gdHJpbW1lZF9ib29rc192MSAlPiUgCiAgYXJyYW5nZShyYXRpbmdzX2NvdW50KQpgYGAKCgpgYGB7cn0KIyBSZW1vdmluZyB0aGUgYm90dG9tIGFuZCB0b3AgNSUgb2YgcmF0aW5ncyBjb3VudApudW1fcm93cyA8LSBucm93KHRyaW1tZWRfYm9va3NfdjIpCmJvdHRvbSA8LSAwLjA1CnRvcCA8LSAwLjk1Cgpib3R0b21fcmVtb3ZlIDwtIHJvdW5kKG51bV9yb3dzICogYm90dG9tKQp0b3BfcmVtb3ZlIDwtIHJvdW5kKG51bV9yb3dzICogKDEgLSB0b3ApKQoKIyBSZW1vdmUgdGhlIGJvdHRvbSBhbmQgdG9wIHJvd3MKdHJpbW1lZF9ib29rc192MiA8LSB0cmltbWVkX2Jvb2tzX3YyWyhib3R0b21fcmVtb3ZlICsgMSk6KG51bV9yb3dzIC0gdG9wX3JlbW92ZSksICwgZHJvcCA9IEZBTFNFXQpgYGAKCmBgYHtyfQp0cmltbWVkX2Jvb2tzX3YyICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBudW1fcGFnZXMsIHkgPSByYXRpbmdzX2NvdW50KSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh4ID0gIk51bWJlciBvZiBQYWdlcyIsIHkgPSAiTnVtYmVyIG9mIFJhdGluZ3MiKQpgYGAKCmBgYHtyfQptb2RlbCA8LSBsbShyYXRpbmdzX2NvdW50IH4gbnVtX3BhZ2VzLCBkYXRhID0gdHJpbW1lZF9ib29rc192MikKc3VtbWFyeShtb2RlbCkKYGBgCmBgYHtyfQpwbG90KG1vZGVsKQpgYGAKCmBgYHtyfQpkYXRhMSA8LSB0cmltbWVkX2Jvb2tzX3YyICU+JQogIGFkZF9wcmVkaWN0aW9ucyhtb2RlbCkgCgpkYXRhMSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBudW1fcGFnZXMpKSArCiAgZ2VvbV9wb2ludChhZXMoeSA9IHJhdGluZ3NfY291bnQpKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZCksIGNvbCA9ICJyZWQiKQpgYGAKCmBgYHtyfQp0cmltbWVkX2Jvb2tzX3YyICU+JSAKc3VtbWFyaXNlKHNrZXduZXNzID0gc2tld25lc3MocmF0aW5nc19jb3VudCwgdHlwZSA9IDEpKQpgYGAKClRoZSBkYXRhIGlzIGhpZ2hseSByaWdodCBza2V3ZWQuIEluIHRoaXMgY2FzZSB3ZSBzaG91bGQgZWl0aGVyIGxvb2sgYXQgdGhlIG1lZGlhbiBhcyBvdXIgc3RhdCwgb3IgbG9vayBhdCBzdGFuZGFyZGlzaW5nIHRoZSBkYXRhLgoKYGBge3J9CnN1bW1hcnkodHJpbW1lZF9ib29rc192MikKYGBgCm51bV9wYWdlcyBub3cgcmFuZ2VzIGZyb20gNTEtNzUwLgoKVXNlIHRoaXMgdG8gYWRkIGJvb2sgcmFuZ2UgY2F0ZWdvcmllczoKCmBgYHtyfQp0cmltbWVkX2Jvb2tzX3dpdGhfcmFuZ2UgPC0gdHJpbW1lZF9ib29rc192MiAlPiUgCiAgbXV0YXRlKHBhZ2VfcmFuZ2UgPSBjYXNlX3doZW4oCiAgICAgIG51bV9wYWdlcyA8PSAxNTAgfiAiNTEtMTUwIiwKICAgICAgbnVtX3BhZ2VzIDw9IDI1MCB+ICIxNTEtMjUwIiwKICAgICAgbnVtX3BhZ2VzIDw9IDM1MCB+ICIyNTEtMzUwIiwKICAgICAgbnVtX3BhZ2VzIDw9IDQ1MCB+ICIzNTEtNDUwIiwKICAgICAgbnVtX3BhZ2VzIDw9IDU1MCB+ICI0NTEtNTUwIiwKICAgICAgbnVtX3BhZ2VzIDw9IDY1MCB+ICI1NTEtNjUwIiwKICAgICAgbnVtX3BhZ2VzIDw9IDc1MCB+ICI2NTEtNzUwIgogICkpICU+JSAKICBtdXRhdGUocGFnZV9yYW5nZSA9IGZhY3RvcihwYWdlX3JhbmdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCI1MS0xNTAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjE1MS0yNTAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyNTEtMzUwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMzUxLTQ1MCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjQ1MS01NTAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI1NTEtNjUwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiNjUxLTc1MCIpKSkKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCgpzYW1wbGVkX3RyaW1tZWRfYm9va3Nfd2l0aF9yYW5nZSA8LSB0cmltbWVkX2Jvb2tzX3dpdGhfcmFuZ2UgJT4lIGdyb3VwX2J5KHBhZ2VfcmFuZ2UpICU+JSBzbGljZV9zYW1wbGUobj0xMDApCgpgYGAKCmBgYHtyfQpzYW1wbGVkX3RyaW1tZWRfYm9va3Nfd2l0aF9yYW5nZSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcGFnZV9yYW5nZSwgeSA9IHJhdGluZ3NfY291bnQpKSArCiAgZ2VvbV9jb2woKQpgYGAKCmBgYHtyfQoodHJpbW1lZF9yYW5nZV9zdW1tYXJ5IDwtIHNhbXBsZWRfdHJpbW1lZF9ib29rc193aXRoX3JhbmdlICU+JSAKICBncm91cF9ieShwYWdlX3JhbmdlKSAlPiUgCiAgc3VtbWFyaXNlKG1lZGlhbl9yYXRpbmdzX2NvdW50ID0gbWVkaWFuKHJhdGluZ3NfY291bnQpLAogICAgICAgICAgICBudW1fYm9va3MgPSBuKCkpKQpgYGAKCmBgYHtyfQp0cmltbWVkX2Jvb2tzX3dpdGhfcmFuZ2Vfbm9ybWFsaXNlZCA8LSB0cmltbWVkX2Jvb2tzX3dpdGhfcmFuZ2UgJT4lCiAgbXV0YXRlKGxvZ19yYXRpbmdzX2NvdW50ID0gbG9nKHJhdGluZ3NfY291bnQpKQpgYGAKCmBgYHtyfQp0cmltbWVkX2Jvb2tzX3dpdGhfcmFuZ2Vfbm9ybWFsaXNlZCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcGFnZV9yYW5nZSwgeSA9IGxvZ19yYXRpbmdzX2NvdW50KSkgKwogIGdlb21fY29sKCkKYGBgCgpgYGB7cn0KKHRyaW1tZWRfcmFuZ2Vfbm9ybWFsaXNlZF9zdW1tYXJ5IDwtIHRyaW1tZWRfYm9va3Nfd2l0aF9yYW5nZV9ub3JtYWxpc2VkICU+JSAKICBncm91cF9ieShwYWdlX3JhbmdlKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKGxvZ19yYXRpbmdzX2NvdW50KSwKICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKGxvZ19yYXRpbmdzX2NvdW50KSwKICAgICAgICAgICAgbnVtX2Jvb2tzID0gbigpKSkKYGBgCgoKCgo=